#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h> 
#include <stdlib.h>
#include <string.h>
#include <sys/times.h>
#include <sys/file.h>
#include "hw2_syscalls_tester.h"


/*  Thanks to Roy Velich for the infrastructure and inspiration !!!!  */

struct sched_param{
	int sched_priority;
	int trial_num;
};
#define IDLE_LOOP_LENGTH 100000
#define SCHED_OTHER		0
#define SCHED_FIFO		1
#define SCHED_RR		2
#define SCHED_MQ		4

#define NEW_LINE(pid)			if(getpid() == pid)	\
	write(fd,"\n",1);

#define TEST(test)	do {					\
	int status;						\
		if(!fork()) 					\
		{						\
			test(); 				\
			while( wait(&status) != -1);		\
			return 0;				\
		}						\
		else 						\
		{						\
			while( wait(&status) != -1); 		\
			print_footer();				\
		}						\
		} while(0)
						
/*#define RUN_TEST(test,pid)		if(getpid() == pid)	\
		{						\
			TEST(test);				\
			int status;				\
			while( wait(&status) != -1); 		\
			if(getpid() == pid)			\
				print_footer();			\
		}						\
		else						\
			return 0;*/
									
#define SECONDS(x) ((int)(x*1000000))
#define SECONDS_IN_MILI(x) ((int)(x*1000))
#define LINE_LENGTH 110

int fd;
char str[LINE_LENGTH + 1];

struct sched_param param;

struct sched_param param_out;

void convert_policy_to_string(int policy,char* str)
{
	if(policy == SCHED_OTHER)
		sprintf(str,"%s","OTHER");
		
	if(policy == SCHED_FIFO)
		sprintf(str,"%s","FIFO");
		
	if(policy == SCHED_RR)
		sprintf(str,"%s","RR");

	if(policy == SCHED_MQ)
		sprintf(str,"%s","MQ");
}

void print_header_params(char *header)
{
	sprintf(str,"+------------------------------------------------------------------------------------------------------------+\n");
	write(fd,str,sizeof(str));
	sprintf(str,"|%-108s|\n",header);
	write(fd,str,sizeof(str));
	sprintf(str,"+-----------------------------------------------------------------+---------------+-----------------+--------|\n");	
	write(fd,str,sizeof(str));
	sprintf(str,"|test desc.                                                       |actual ret val |expected ret val |result  |\n");
	write(fd,str,sizeof(str));
	sprintf(str,"+-----------------------------------------------------------------+---------------+-----------------+--------|\n");
	write(fd,str,sizeof(str));	
}

void print_message_params(char *desc,char *actual,char *expected,char *res)
{
	sprintf(str,"|%-65s|%-15s|%-17s|%-8s|\n",desc,actual,expected,res);
	write(fd,str,sizeof(str));		
}

void print_header(char *header)
{
	sprintf(str,"+------------------------------------------------------------------------------------------------------------+\n");	
	write(fd,str,sizeof(str));
	sprintf(str,"|%-108s|\n",header);
	write(fd,str,sizeof(str));
	sprintf(str,"+---------+--------+--------+-----------+-----------+--------------------------------------------------------|\n");	
	write(fd,str,sizeof(str));
	sprintf(str,"|time     |pid     |policy  |rem trial |time slce   |message                                                 |\n");
	write(fd,str,sizeof(str));
	sprintf(str,"+---------+--------+--------+-----------+-----------+--------------------------------------------------------|\n");
	write(fd,str,sizeof(str));		
}

void print_footer()
{
	sprintf(str,"+------------------------------------------------------------------------------------------------------------+\n");
	write(fd,str,sizeof(str));			
}

void print_message_by_pid(char *msg,int pid)
{
	char policy_str[10];
	int policy = sched_getscheduler(pid);
	int number_trials = number_of_trials(pid); 
	int time_slice = mq_time_slice(pid); 	
	convert_policy_to_string(policy,policy_str);
	
	struct tms times_struct;
	int ticks = times(&times_struct);	
		
	if(policy != SCHED_MQ)
	{
		sprintf(str,"|%-9d|%-8d|%-8s|%-11s|%-11s|%-56s|\n",ticks,pid,policy_str,"N/A","N/A",msg);
		write(fd,str,sizeof(str));
	}
	else if((policy == SCHED_MQ) && (number_trials))
	{
		sprintf(str,"|%-9d|%-8d|%-8s|%-11d|%-11d|%-56s|\n",ticks,pid,policy_str,number_trials,time_slice,msg);
		write(fd,str,sizeof(str));
	}
	else
	{
		sprintf(str,"|%-9d|%-8d|%-8s|%-11d|%-11d|%-56s|\n",ticks,pid,policy_str,0,time_slice,msg);	
		write(fd,str,sizeof(str));
	}			
}

void print_message(char *msg)
{
	print_message_by_pid(msg,getpid());
}

void idle_loop(int loops)
{
	int counter;
	long i;
	for(counter = 0; counter < loops; counter++)
	{
		for(i = 0; i < IDLE_LOOP_LENGTH; i++) {}
	}
}

void test_real_time_fifo_vs_real_time_fifo()
{
	printf("Test 1\n");
	print_header("TEST 1 - REAL-TIME FIFO VS. REAL-TIME FIFO:");
	int pids[3];
	param.sched_priority = 99;
	sched_setscheduler(getpid(),SCHED_FIFO,&param);	
	int res = fork();
	pids[0] = res;
	if(res)
	{
		res = fork();
		pids[1] = res;		
		if(res)
		{
			res = fork();
			pids[2] = res;			
			if(res)
			{
				param.sched_priority = 10;
				sched_setscheduler(pids[2],SCHED_FIFO,&param);		

				param.sched_priority = 30;
				sched_setscheduler(pids[0],SCHED_FIFO,&param);	
				
				param.sched_priority = 35;
				sched_setscheduler(pids[1],SCHED_FIFO,&param);	
			
				print_message("1");
			}
			else
			{
				print_message("4");
			}
		}
		else
		{
			print_message("2");
		}
	}
	else
	{
		print_message("3");
	}	
}

void test_real_time_fifo_vs_other()
{
	printf("Test 2\n");
	print_header("TEST 2 - REAL-TIME FIFO VS. OTHER: (FIFO SHOULD DIE BEFORE OTHER)");
	int res = fork();
	if(!res)
	{
		print_message("2");
	}
	else
	{
		param.sched_priority = 50;
		sched_setscheduler(getpid(),SCHED_FIFO,&param);	
		sched_yield();
		print_message("1");
	}
}

void test_real_time_rr_vs_other()
{
	printf("Test 3\n");
	print_header("TEST 3 - REAL-TIME RR VS. OTHER:");
	int res = fork();
	if(!res)
	{
		print_message("2");
	}
	else
	{
		param.sched_priority = 50;
		sched_setscheduler(getpid(),SCHED_RR,&param);	
		sched_yield();
		print_message("1");
	}
}

void test_real_time_fifo_vs_mq()
{
	printf("Test 4\n");
	print_header("TEST 4 - REAL-TIME FIFO VS. REGULAR MQ:");
	int res = fork();
	if(res)
	{
		param.sched_priority = 0;
		param.trial_num = 100;
		sched_setscheduler(getpid(),SCHED_MQ,&param);	

		param.sched_priority = 50;
		sched_setscheduler(res,SCHED_FIFO,&param);	
		print_message("2");
	}
	else
	{
		sched_yield();
		print_message("1");
	}
}

void test_overdue_mq_vs_other()
{
	printf("Test 5\n");
	print_header("TEST 5 - OVERDUE MQ VS. OTHER:");	
	int res = fork();
	if(res)
	{
		param.sched_priority = 0;
		param.trial_num = 1;
		sched_setscheduler(getpid(),SCHED_MQ,&param);	
		idle_loop(10000);
		print_message("2");
	}
	else
	{
		sched_yield();
		print_message("1");
	}
}

void test_regular_mq_vs_other()
{
	printf("Test 6\n");
	print_header("TEST 6 - REGULAR MQ VS. OTHER:");		
	int res = fork();
	if(res)
	{
		param.sched_priority = 0;
		param.trial_num = 1;
		sched_setscheduler(getpid(),SCHED_MQ,&param);	
		print_message("1");	
	}
	else
	{
		print_message("2");
	}
}

void test_regular_mq_vs_regular_mq()
{
	printf("Test 7\n");
	print_header("TEST 7 - REGULAR MQ VS. REGULAR MQ:");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	int res = fork();
	if(res)
	{
		print_message("2");
	}
	else
	{
		print_message("1");
	}
}



void test_regular_mq_vs_overdue_mq()
{
	printf("Test 8\n");
	print_header("TEST 8 - REGULAR MQ VS. OVERDUE MQ:");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	int res = fork();
	if(res)
	{
		print_message("2");
	}
	else
	{		
		print_message("1");
		idle_loop(10000);
		print_message("3");
	}

}

void test_regular_mq_vs_regular_mq_with_fork()
{
	printf("Test 9\n");
	print_header("TEST 9 - REGULAR MQ VS. REGULAR MQ + FORK:");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	print_message("1");
	int res = fork();
	if(res)
	{
		print_message("3");
		res = fork();
		if(res)
		{
			print_message("5");			
		}
		else
		{
			print_message("4");								
		}
	}
	else
	{
		print_message("2");				
	}
}

void test_overdue_mq_fork_vs_overdue_mq_fork()
{
	printf("Test 10\n");
	print_header("TEST 10 - OVERDUE MQ + FORK VS. OVERDUE MQ + FORK:");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	int res = fork();
	if(res)
	{
		print_message("2");
		idle_loop(10000);
		print_message("4");
	}
	else
	{		
		print_message("1");
		idle_loop(10000);
		print_message("3");
	}

}

void test_overdue_mq_fork_vs_overdue_mq_fork2()
{
	printf("Test 11\n");
	print_header("TEST 11 - OVERDUE MQ + FORK VS. OVERDUE MQ + FORK - 2:");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	idle_loop(10000);	
	int res = fork();
	if(res)
	{
		print_message("1");
	}
	else
	{		
		print_message("2");
	}

}
void test_overdue_processes_fifo()
{
	printf("Test 12\n");
	print_header("TEST 12 - TESTING OVERDUE MQ PROCESSES FIFO:");	
	int i, res;
	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	idle_loop(10000);
	print_message("1 - father");
	
	res = fork();
	if (res) { /*is father*/
		print_message("2 - father");
		res = fork();
		if (res) {
			print_message("3 - father");
		}
		else {
			print_message("5 - son #2");
		}
	}
	else { /*is son*/
		print_message("4 - son #1");
	}
}

void test_regular_mq_sorting()
{
	printf("Test 13\n");
	print_header("TEST 13 - REGULAR MQ VS. REGULAR MQ (+ /-) NICE");	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	print_message("1");
	int res = fork();
	if(res)
	{
		print_message("2");
		res = fork();
		if(res)
		{
			nice(+15);
			print_message("4");
		}
		else
		{	
			nice(+20);
			print_message("5");				
		}
	}
	else
	{
		nice(+10);
		print_message("3");
	}
}

void test_overdue_processes_fifo_2()
{
	printf("Test 14\n");
	print_header("TEST 14 - TESTING OVERDUE MQ PROCESSES FIFO 2:");
	/* 
	 * this time we are queueing not so related OVERDUEs 
	 * the order: f | s1 | s2 | s1s1 | s1s2 | s2s1 	
	 */
	int i, res;
	
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	idle_loop(10000);
	
	print_message("1 - father");
	res = fork(); // son #1
	if (res) { /*is father*/
		print_message("2 - father");
		res = fork(); // son #2
		if (res) {
			print_message("3 - father");
			res = fork(); // son #3
			if (res) {
				print_message("4 - father");
			}
			else {
				print_message("10 - son #3");
			}
		}
		else {
			print_message("8 - son #2");
			res = fork(); // son's #2 son #1
			if (res) {
				print_message("9 - son #2");
			}
			else {
				print_message("13 - son's #3 son #1");
			}
		}
	} 
	else { /*is son*/
		print_message("5 - son #1");
		res = fork(); // son's #1 son #1
		if (res) {
			print_message("6 - son #1");
			res = fork(); // son's #1 son #2
			if (res) {
				print_message("7 - son #1");
			}
			else {
				print_message("12 - son's #3 son #1");
			}
		}
		else {
			print_message("11 - son's #3 son #1");
		}
	}
}



void test_MQ_processes_fifo()
{
	printf("Test 15\n");
	print_header("TEST 15 - TESTING MQ PROCESSES (SEMI) FIFO:");
	/*
	 * depends on implementation: 
	 * didEnqueue == 1 ---> works if son just queued before the father
	 * didEnqueue != 1 ---> works if son always takes the CPU right after it was created
	 */
	int didEnqueue = 1;
	int i, res;
	
	param.sched_priority = 0;
	param.trial_num = 10;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
	
	if (didEnqueue) {
		print_message("1 - father");
		res = fork(); // son #1
		if (res) {
			print_message("3 - father");
			res = fork(); // son #2
			if (res) {
				print_message("7 - father");
				res = fork(); // son #3
				if (res) {
					print_message("13 - father");
				}
				else {
					print_message("12 - son #3");
				}
			} 
			else { 
				print_message("6 - son #2");
				res = fork(); // son's #2 son #1
				if (res) {
					print_message("11 - son #2");
				}
				else {
					print_message("10 - son's #2 son #1");
				}
			}
		} 
		else {
			print_message("2 - son #1");
			res = fork(); // son's #1 son #1
			if (res) {
				print_message("5 - son #1"); 
				res = fork(); // son's #1 son #2
				if (res) {
					print_message("9 - son #1"); 
				}
				else {
					print_message("8 - son's #1 son #2");
				}
			}
			else {
				print_message("4 - son's #1 son #1");
			}
		}
	}
	else {
		print_message("1 - father");
		res = fork(); // son #1
		if (res) {
			print_message("4 - father");
			res = fork(); // son #2
			if (res) {
				print_message("9 - father");
				res = fork(); // son #3
				if (res) {
					print_message("13 - father");
				}
				else {
					print_message("10 - son #3");
				}
			} 
			else { 
				print_message("5 - son #2");
				res = fork(); // son's #2 son #1
				if (res) {
					print_message("11 - son #2");
				}
				else {
					print_message("6 - son's #2 son #1");
				}
			}
		} 
		else {
			print_message("2 - son #1");
			res = fork(); // son's #1 son #1
			if (res) {
				print_message("7 - son #1"); 
				res = fork(); // son's #1 son #2
				if (res) {
					print_message("12 - son #1"); 
				}
				else {
					print_message("8 - son's #1 son #2");
				}
			}
			else {
				print_message("3 - son's #1 son #1");
			}
		}
	}
}


void test_trials()
{
	printf("Test 16\n");
	print_header("TEST 16 - USING ALL TRIALS BEFORE OTHER");	
	param.sched_priority = 0;
	param.trial_num = 2;
	int res = fork();
	if (res) 
	{
		sched_setscheduler(getpid(),SCHED_MQ,&param);
		print_message("1");
		idle_loop(10000); 
		if(!is_mq(getpid()))
			print_message("3");
			
	} else { //THIS IS OTHER
		print_message("2");
	}
	
}
/***************************************************************************************/

void print_overdue_progress(int i)
{
	param.sched_priority = 0;
	param.trial_num = 1;
	sched_setscheduler(getpid(),SCHED_MQ,&param);
//	idle_time_sp(SECONDS_IN_MILI(1.1));	

	char str[20];
	sprintf(str,"Overdue #%d is running!",i);
	
	print_message(str);
//	idle_time_sp(SECONDS_IN_MILI(0.5));	
	print_message(str);
//	idle_time_sp(SECONDS_IN_MILI(0.5));	
	print_message(str);
//	idle_time_sp(SECONDS_IN_MILI(0.5));	
	print_message(str);
//	idle_time_sp(SECONDS_IN_MILI(0.5));	
	print_message(str);	
}


void convert_error(char *error_str)
{
	switch(errno)
	{
		case EINVAL:
			sprintf(error_str,"EINVAL");
			break;
		case EFAULT:
			sprintf(error_str,"EFAULT");
			break;
		case ESRCH:
			sprintf(error_str,"ESRCH");	
			break;
		case EPERM:
			sprintf(error_str,"EPERM");	
			break;		
	}
}

#define PRINT_RESULT(desc,actual,expected,condition)		do 																\
															{																\
																if(condition) 												\
																	print_message_params(desc,actual,expected,"OK"); 		\
																else 														\
																	print_message_params(desc,actual,expected,"FAIL");		\
															} 																\
															while(0)



void test_parameters()
{
	int res;
	int pid = getpid();
	char actual[10];
	
	print_header_params("TESTING PARAMETERS AND RETURN VALUES");
	
	param.sched_priority = 0;
	param.trial_num = 0;
	res = sched_setscheduler(pid,SCHED_MQ,&param);
	convert_error(actual);
	PRINT_RESULT("Trying to set SCHED_MQ with Trial Num = 0",actual,"EINVAL",((res == -1) && (errno == EINVAL)));
	
	res = sched_getscheduler(pid);
	convert_policy_to_string(res,actual);
	PRINT_RESULT("Checking that policy did not change...",actual,"OTHER",(res == SCHED_OTHER));	
	
	param.sched_priority = 0;
	param.trial_num = 101;
	res = sched_setscheduler(pid,SCHED_MQ,&param);
	convert_error(actual);
	PRINT_RESULT("Trying to set SCHED_MQ with Trial Num = 101",actual,"EINVAL",((res == -1) && (errno == EINVAL)));	
			
	res = sched_getscheduler(pid);
	convert_policy_to_string(res,actual);
	PRINT_RESULT("Checking that policy did not change...",actual,"OTHER",(res == SCHED_OTHER));	


	
			
	param.sched_priority = 0;
	param.trial_num = 1;
	res = sched_setscheduler(pid,SCHED_MQ,&param);	
	sprintf(actual,"%d",res);
	PRINT_RESULT("Trying to set SCHED_MQ with Trial Num = 1",actual,"0",(res == 0));

	res = sched_getscheduler(pid);
	convert_policy_to_string(res,actual);
	PRINT_RESULT("Checking that policy did change...",actual,"MQ",(res == SCHED_MQ));	
	
	res = sched_yield();
	sprintf(actual,"%d",res);	
	PRINT_RESULT("Trying to yield a mq process...",actual,"-1",(res == -1));	
		
	param.sched_priority = 20;
	errno = 0;
	res = sched_setscheduler(pid,SCHED_FIFO,&param);	
	convert_error(actual);	
	PRINT_RESULT("Trying to set a MQ process to SCHED_FIFO",actual,"EPERM",((res == -1) && (errno == EPERM)));		

	param.sched_priority = 20;
	errno = 0;
	res = sched_setscheduler(pid,SCHED_RR,&param);	
	convert_error(actual);	
	PRINT_RESULT("Trying to set a MQ process to SCHED_RR",actual,"EPERM",((res == -1) && (errno == EPERM)));

	param.sched_priority = 0;
	errno = 0;
	res = sched_setscheduler(pid,SCHED_OTHER,&param);
	convert_error(actual);	
	PRINT_RESULT("Trying to set a MQ process to SCHED_OTHER",actual,"EPERM",((res == -1) && (errno == EPERM)));

	param.sched_priority = 0;
	errno = 0;
	param.trial_num = 90;
	res = sched_setparam(pid,&param);	
	convert_error(actual);	
	PRINT_RESULT("Trying to change the Trial Num of SCHED_MQ to 90",actual,"EPERM",((res == -1) && (errno == EPERM)));

	res = sched_getparam(pid,&param_out);
	sprintf(actual,"%d",param_out.sched_priority);
	PRINT_RESULT("Checking that Trial Num did not change...",actual,"1",((param_out.sched_priority == 0) && (res == 0)));
		
}


int main(int argc, char* argv[])
{
	printf("Running test, please wait... (It may take more than one minute).\n");
	
	fd = open("out", O_WRONLY | O_CREAT | O_TRUNC,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	int pid = getpid();	

	TEST(test_parameters);	
	NEW_LINE(pid);		
	TEST(test_real_time_fifo_vs_real_time_fifo);
	NEW_LINE(pid);
	TEST(test_real_time_fifo_vs_other);
	NEW_LINE(pid);	
	TEST(test_real_time_rr_vs_other);
	NEW_LINE(pid);
	TEST(test_real_time_fifo_vs_mq);
	NEW_LINE(pid);		
	TEST(test_overdue_mq_vs_other);
	NEW_LINE(pid);	
	TEST(test_regular_mq_vs_other);
	NEW_LINE(pid);	
	TEST(test_regular_mq_vs_regular_mq);		
	NEW_LINE(pid);		
	TEST(test_regular_mq_vs_overdue_mq);
	NEW_LINE(pid);			
	TEST(test_regular_mq_vs_regular_mq_with_fork);	
	NEW_LINE(pid);		
	TEST(test_overdue_mq_fork_vs_overdue_mq_fork);
	NEW_LINE(pid);
	TEST(test_overdue_mq_fork_vs_overdue_mq_fork2);
	NEW_LINE(pid);
	TEST(test_overdue_processes_fifo);
	NEW_LINE(pid);
	TEST(test_regular_mq_sorting);
	NEW_LINE(pid);	
	TEST(test_overdue_processes_fifo_2);
	NEW_LINE(pid);	
	TEST(test_MQ_processes_fifo);
	NEW_LINE(pid);	
	TEST(test_trials);
	NEW_LINE(pid);

	close(fd);
	
	//printf("Test finished fool, output was redirected to a file named 'out'.\n");	
	
	return 0;
}
